package v1

import (
	"context"
	"fmt"
	"reflect"

	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

// pvc声明信息
type OwnConfigMap struct {
	Name       string            `json:"name"`
	Data       map[string]string `json:"data,omitempty" protobuf:"bytes,2,rep,name=data"`
	BinaryData map[string][]byte `json:"binaryData,omitempty" protobuf:"bytes,3,rep,name=binaryData"`
}

func (o *OwnConfigMap) MakeOwnResource(instance *IngressNginx, logger logr.Logger,
	scheme *runtime.Scheme) (interface{}, error) {

	// new a ConfigMap object
	cm := &corev1.ConfigMap{
		// metadata field inherited from owner Unit
		ObjectMeta: metav1.ObjectMeta{Name: o.Name, Namespace: instance.Namespace, Labels: instance.Labels},
		Data:       o.Data,
		BinaryData: o.BinaryData,
	}

	// add ControllerReference for sts，the owner is Unit object
	if err := controllerutil.SetControllerReference(instance, cm, scheme); err != nil {
		msg := fmt.Sprintf("set controllerReference for %s %s/%s failed", cm.Kind, cm.Namespace, cm.Name)
		logger.Error(err, msg)
		return nil, err
	}

	return cm, nil
}

// Check if the ownPVC already exists
func (o *OwnConfigMap) OwnResourceExist(instance *IngressNginx, client client.Client,
	logger logr.Logger) (bool, interface{}, error) {

	found := &corev1.ConfigMap{}
	err := client.Get(context.TODO(), types.NamespacedName{Name: o.Name, Namespace: instance.Namespace}, found)
	if err != nil {
		if errors.IsNotFound(err) {
			return false, nil, nil
		}

		msg := fmt.Sprintf("%s %s/%s found, but with error", found.Kind, instance.Namespace, instance.Name)
		logger.Error(err, msg)
		return true, found, err
	}
	return true, found, nil
}

func (o *OwnConfigMap) UpdateOwnResourceStatus(instance *IngressNginx, client client.Client,
	logger logr.Logger) (*IngressNginx, error) {

	found := &corev1.ConfigMap{}
	err := client.Get(context.TODO(), types.NamespacedName{Name: o.Name, Namespace: instance.Namespace}, found)
	if err != nil {
		if errors.IsNotFound(err) {
			instance.Status.LastUpdateTime = metav1.Now()
			return instance, nil
		}
		return instance, err
	}

	return instance, nil
}

// apply this own resource, create or update
func (o *OwnConfigMap) ApplyOwnResource(instance *IngressNginx, client client.Client,
	logger logr.Logger, scheme *runtime.Scheme) error {

	// assert if PVC exist
	exist, found, err := o.OwnResourceExist(instance, client, logger)
	if err != nil {
		return err
	}

	// make PVC object
	cm, err := o.MakeOwnResource(instance, logger, scheme)
	if err != nil {
		return err
	}
	newCM := cm.(*corev1.ConfigMap)

	// apply the PVC object just make
	if !exist {
		// if PVC not exist，then create it
		msg := fmt.Sprintf("PVC %s/%s not found, create it!", newCM.Namespace, newCM.Name)
		logger.Info(msg)

		if err := client.Create(context.TODO(), newCM); err != nil {
			return err
		}
		return nil
	} else {
		foundCM := found.(*corev1.ConfigMap)
		// if PVC exist with change，then try to update it
		if !reflect.DeepEqual(newCM.Data, foundCM.Data) {
			msg := fmt.Sprintf("Updating %s %s/%s", newCM.Kind, newCM.Namespace, newCM.Name)
			logger.Info(msg)
			return client.Update(context.TODO(), newCM)
		}
		return nil
	}
}
